package com.sirius.botasky.bledemo; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.os.Bundle; import android.support.constraint.ConstraintLayout; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.ExpandableListView; import android.widget.LinearLayout; import android.widget.SimpleExpandableListAdapter; import android.widget.TextView; import android.widget.Toast; import com.sirius.botasky.cyberble.ble.BleAdmin; import com.sirius.botasky.cyberble.ble.BleDeviceService; import com.sirius.botasky.cyberble.callback.DeviceConnectStateCallback; import com.sirius.botasky.cyberble.callback.DeviceOperationCallback; import com.sirius.botasky.cyberble.callback.ScanCallback; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import static com.sirius.botasky.bledemo.ConnectActivity.UUID_HEART_RATE_MEASUREMENT; public class LibTestActivity extends AppCompatActivity { public static final String TAG = LibTestActivity.class.getSimpleName(); private Button mScanButton, mStopButton, mDisconnecte, mWrite; private EditText mEditData; private ExpandableListView mExpanListView; private RecyclerView mDevicesRecycler; private BleDeviceAdapter mRecyclerAdapter; private BleAdmin mBleAdmin; private TextView mNotifyData; private ConstraintLayout discover, connect; private String mCurrentDeviceAddress; private BluetoothGattCharacteristic mNotifyCharacteristic; private DeviceConnectStateCallback mDeviceCallBack = new DeviceConnectStateCallback() { @Override public void onDeviceConnected(final String address) { runOnUiThread(new Runnable() { @Override public void run() { //连接成功后去发现设备的服务。停止搜索 mBleAdmin.discoverDeviceServices(address); mBleAdmin.stopScan(); discover.setVisibility(View.GONE); connect.setVisibility(View.VISIBLE); } }); } @Override public void onDeviceDisconnected(String address) { runOnUiThread(new Runnable() { @Override public void run() { discover.setVisibility(View.VISIBLE); connect.setVisibility(View.GONE); } }); } }; private DeviceOperationCallback mDeviceOperationCallback = new DeviceOperationCallback() { @Override public void onDeviceServiceDiscover(boolean isSuccess, String deviceAddress, List<BluetoothGattService> services) { if (isSuccess) { displayGattServices(services); } } @Override public void onDeviceCharacteristicRead(boolean isSuccess, String deviceAddress, BluetoothGattCharacteristic characteristic) { if (isSuccess) { displayData(characteristic); } } @Override public void onDeviceCharacteristicWrite(boolean isSuccess, String deviceAddress, BluetoothGattCharacteristic characteristic) { } @Override public void onDeviceCharacteristicNotify(String deviceAddress, BluetoothGattCharacteristic characteristic) { displayData(characteristic); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_discover); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); mBleAdmin = new BleAdmin(this, mDeviceCallBack, mDeviceOperationCallback); setupView(); } private void setupView() { mScanButton = (Button) findViewById(R.id.start_scan); mDevicesRecycler = ((RecyclerView) findViewById(R.id.device_list)); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); mDevicesRecycler.setLayoutManager(linearLayoutManager); mRecyclerAdapter = new BleDeviceAdapter(); mDevicesRecycler.setAdapter(mRecyclerAdapter); mStopButton = (Button) findViewById(R.id.stop_scan); mDisconnecte = (Button) findViewById(R.id.diconnect); mWrite = (Button) findViewById(R.id.write); mEditData = (EditText) findViewById(R.id.write_data); mExpanListView = (ExpandableListView) findViewById(R.id.gatt_services_list); mExpanListView.setOnChildClickListener(servicesListClickListner); mNotifyData = (TextView) findViewById(R.id.data); mWrite.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mBleAdmin.processDeviceService(new BleDeviceService( mCurrentDeviceAddress, UUID.fromString(SampleGattAttributes.UUID_WRITE), BleDeviceService.OperateType.Write, mEditData.getText().toString().getBytes())); } }); discover = (ConstraintLayout) findViewById(R.id.discover); connect = (ConstraintLayout) findViewById(R.id.connect_layout); mDisconnecte.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mBleAdmin.disconnectDevice(mCurrentDeviceAddress); } }); //开始扫描 mScanButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mBleAdmin.openBle(); mBleAdmin.startScanAllDevice(new ScanCallback() { @Override public void onDeviceFound(final List<BluetoothDevice> devices) { Log.e("Found", " " + devices.size()); runOnUiThread(new Runnable() { @Override public void run() { mRecyclerAdapter.setDevices(devices); } }); } }); } }); mStopButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mBleAdmin.stopScan(); } }); } // If a given GATT characteristic is selected, check for supported features. This sample // demonstrates 'Read' and 'Notify' features. See // http://d.android.com/reference/android/bluetooth/BluetoothGatt.html for the complete // list of supported characteristic features. /** * 如果一个GATT的特征被选中, */ private final ExpandableListView.OnChildClickListener servicesListClickListner = new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { if (mGattCharacteristics != null) { final BluetoothGattCharacteristic characteristic = mGattCharacteristics.get(groupPosition).get(childPosition); final int charaProp = characteristic.getProperties(); if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) { // If there is an active notification on a characteristic, clear // it first so it doesn't update the data field on the user interface. //如果当前有一个NOTIFY的特征正在活动,要先关闭NOTIFY特征,再去开启READ特征 if (mNotifyCharacteristic != null) { // setCharacteristicNotification(mNotifyCharacteristic, false); mNotifyCharacteristic = null; } Toast.makeText(LibTestActivity.this, "这是一个READ特征,开启读特征", Toast.LENGTH_SHORT).show(); //开启读特征 mBleAdmin.processDeviceService(new BleDeviceService( mCurrentDeviceAddress, characteristic.getUuid(), BleDeviceService.OperateType.Read)); // readCharacteristic(characteristic); } if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { mNotifyCharacteristic = characteristic; Toast.makeText(LibTestActivity.this, "这是一个NOTIFY特征,开启读特征", Toast.LENGTH_SHORT).show(); mBleAdmin.processDeviceService(new BleDeviceService(mCurrentDeviceAddress, characteristic.getUuid(), BleDeviceService.OperateType.Notify)); } if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) != 0 && (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0){ Toast.makeText(LibTestActivity.this, "这是一个WRITE特征", Toast.LENGTH_SHORT).show(); } return true; } return false; } }; private final String LIST_NAME = "NAME"; private final String LIST_UUID = "UUID"; private ArrayList<ArrayList<BluetoothGattCharacteristic>> mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>(); /** * 将获取到的服务展示出来,根据SampleGattAttributes对比,看有;没有符合的服务 */ private void displayGattServices(List<BluetoothGattService> services) { if (services == null) return; String uuid = null; String unknownServiceString = "未知服务"; String unknownCharaString = "未知特征"; ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>(); ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData = new ArrayList<ArrayList<HashMap<String, String>>>(); mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>(); for (BluetoothGattService gattService : services) { HashMap<String, String> currentServiceData = new HashMap<String, String>(); uuid = gattService.getUuid().toString(); currentServiceData.put( LIST_NAME, SampleGattAttributes.lookup(uuid, unknownServiceString)); currentServiceData.put(LIST_UUID, uuid); gattServiceData.add(currentServiceData); ArrayList<HashMap<String, String>> gattCharacteristicGroupData = new ArrayList<HashMap<String, String>>(); List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics(); ArrayList<BluetoothGattCharacteristic> charas = new ArrayList<BluetoothGattCharacteristic>(); // Loops through available Characteristics. for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { charas.add(gattCharacteristic); HashMap<String, String> currentCharaData = new HashMap<String, String>(); uuid = gattCharacteristic.getUuid().toString(); currentCharaData.put( LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString)); currentCharaData.put(LIST_UUID, uuid); gattCharacteristicGroupData.add(currentCharaData); } mGattCharacteristics.add(charas); gattCharacteristicData.add(gattCharacteristicGroupData); } final SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter( this, gattServiceData, android.R.layout.simple_expandable_list_item_2, new String[]{LIST_NAME, LIST_UUID}, new int[]{android.R.id.text1, android.R.id.text2}, gattCharacteristicData, android.R.layout.simple_expandable_list_item_2, new String[]{LIST_NAME, LIST_UUID}, new int[]{android.R.id.text1, android.R.id.text2} ); runOnUiThread(new Runnable() { @Override public void run() { mExpanListView.setAdapter(gattServiceAdapter); } }); } /** * 展示READ或者NOTIFY特征返回的信息 * * @param */ private void displayData(BluetoothGattCharacteristic characteristic) { /** * 通用心率协议的NOTIFY特征需要特殊处理 */ // This is special handling for the Heart Rate Measurement profile. Data parsing is // carried out as per profile specifications: // http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { int flag = characteristic.getProperties(); int format = -1; if ((flag & 0x01) != 0) { format = BluetoothGattCharacteristic.FORMAT_UINT16; Log.d(TAG, "Heart rate format UINT16."); } else { format = BluetoothGattCharacteristic.FORMAT_UINT8; Log.d(TAG, "Heart rate format UINT8."); } final int heartRate = characteristic.getIntValue(format, 1); Log.d(TAG, String.format("Received heart rate: %d", heartRate)); runOnUiThread(new Runnable() { @Override public void run() { mNotifyData.setText(String.valueOf(heartRate)); } }); } else { // For all other profiles, writes the data formatted in HEX. final byte[] data = characteristic.getValue(); if (data != null && data.length > 0) { final StringBuilder stringBuilder = new StringBuilder(data.length); for (byte byteChar : data) stringBuilder.append(String.format("%02X ", byteChar)); runOnUiThread(new Runnable() { @Override public void run() { mNotifyData.setText(new String(data) + "\n" + stringBuilder.toString()); } }); } } } private class BleDeviceAdapter extends RecyclerView.Adapter<BleDeviceAdapter.ViewHolder> { private List<BluetoothDevice> devices; private LayoutInflater mInflator; private Map<String, Integer> mDevicesRssi; public BleDeviceAdapter() { this.devices = new ArrayList<>(); this.mDevicesRssi = new HashMap<>(); this.mInflator = LibTestActivity.this.getLayoutInflater(); } private void setDevices(List<BluetoothDevice> devices) { this.devices = devices; notifyDataSetChanged(); } @Override public BleDeviceAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = mInflator.inflate(R.layout.item_device, parent, false); BleDeviceAdapter.ViewHolder item = new BleDeviceAdapter.ViewHolder(view); return item; } @Override public void onBindViewHolder(BleDeviceAdapter.ViewHolder holder, final int position) { BleDeviceAdapter.ViewHolder viewHolder = ((BleDeviceAdapter.ViewHolder) holder); final BluetoothDevice device = devices.get(position); final String deviceName = device.getName(); if (deviceName != null && deviceName.length() > 0) viewHolder.deviceName.setText(deviceName); else viewHolder.deviceName.setText("Unknow"); viewHolder.deviceAddress.setText(device.getAddress()); viewHolder.content.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mCurrentDeviceAddress = device.getAddress(); // mBleAdmin.connectDevice(device.getAddress()); mBleAdmin.connectDevice(device); } }); } @Override public int getItemCount() { return devices.size(); } class ViewHolder extends RecyclerView.ViewHolder { private LinearLayout content; private TextView deviceName; private TextView deviceAddress; public ViewHolder(View itemView) { super(itemView); content = ((LinearLayout) itemView.findViewById(R.id.content)); deviceAddress = ((TextView) itemView.findViewById(R.id.device_address)); deviceName = (TextView) itemView.findViewById(R.id.device_name); } } } }